package org.budo.dubbo.protocol.http.invocation.adapter;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.budo.dubbo.protocol.http.Consts;
import org.budo.dubbo.protocol.http.cipher.Cipher;
import org.budo.dubbo.protocol.http.cipher.CipherService;
import org.budo.dubbo.protocol.http.dispatcher.servlet.ServletProtocolRequest;
import org.budo.dubbo.protocol.http.exception.BudoRuntimeException;
import org.budo.dubbo.protocol.http.exception.remoting.ArgumentTimeFormatException;
import org.budo.dubbo.protocol.http.exception.remoting.ArgumentTypeMismatchException;
import org.budo.dubbo.protocol.http.exception.remoting.ArrayArgumentMismatchException;
import org.budo.dubbo.protocol.http.exception.remoting.EnumArgumentMismatchException;
import org.budo.dubbo.protocol.http.exception.remoting.ProviderSideRuntimeException;
import org.budo.dubbo.protocol.http.invocation.BudoDubboRpcInvocation;
import org.budo.dubbo.protocol.http.util.BudoBeanUtil;
import org.budo.dubbo.protocol.http.util.BudoReflectionUtil;
import org.budo.support.lang.util.NumberUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.mvcs.Mvcs;
import org.budo.time.Time;
import org.budo.time.TimePoint;
import org.springframework.util.Assert;

import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcInvocation;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Getter
@Setter
@Slf4j
public abstract class AbstractInvocationAdapter implements InvocationAdapter {
    /**
     * 默认支持的几种时间格式
     */
    private String[] timeFormats = Time.DEFAULT_TIME_FORMATS;

    /**
     * 读取请求体
     */
    protected String getProtocolRequestBody(ProtocolRequest protocolRequest) throws IOException {
        HttpServletRequest request = ((ServletProtocolRequest) protocolRequest).getRequest();

        String interfaceName = request.getHeader(Consts.HeaderKey.InterfaceName);
        if (null == interfaceName) {
            interfaceName = (String) request.getAttribute(Consts.HeaderKey.InterfaceName);
        }

        Assert.hasText(interfaceName, "#66 null InterfaceName in header");

        String methodName = request.getHeader(Consts.HeaderKey.MethodName);
        if (null == methodName) {
            methodName = (String) request.getAttribute(Consts.HeaderKey.MethodName);
        }

        Assert.hasText(interfaceName, "#69 null MethodName in header");

        String requestBody = Mvcs.getRequestBody(request);

        log.trace("#88 requestBody={}, request={}", requestBody, request);

        // 这里也不支持方法重载
        Method method = BudoReflectionUtil.getMatchedMethodCached(interfaceName, methodName);
        Cipher cipher = null == method ? null : BudoReflectionUtil.getMethodAnnotationCached(method, Cipher.class);

        if (null == cipher || null == cipher.value() || cipher.value().isEmpty()) {
            return requestBody;
        }

        // 下面为 Cipher 流程
        RpcInvocation rpcInvocation = new RpcInvocation();
        this.readAttachments(protocolRequest, rpcInvocation);

        // Cipher 解密
        BudoDubboRpcInvocation budoInvocation = new BudoDubboRpcInvocation(rpcInvocation, requestBody);
        CipherService cipherService = this.getCipherService(cipher.value());
        budoInvocation = cipherService.decipher(budoInvocation);

        // 返回结果时使用
        request.setAttribute(CipherService.CIPHER_SERVICE_IN_REQUEST_ATTRIBUTE_KEY, cipherService);
        request.setAttribute(BudoDubboRpcInvocation.BUDO_INVOCATION_IN_REQUEST_ATTRIBUTE_KEY, budoInvocation);
        return budoInvocation.getRequestBody();
    }

    protected CipherService getCipherService(String cipherServiceBeanName) {
        CipherService cipherService = (CipherService) BudoBeanUtil.getBeanCached(null, cipherServiceBeanName);

        if (null == cipherService) {
            String message = "#94 cipherService required, this=" + this + ", applicationContext=" + BudoBeanUtil.getApplicationContext();
            throw new ProviderSideRuntimeException(message);
        }

        return cipherService;
    }

    /**
     * 读取特定请求头，放入 invocation.attachments
     */
    protected void readAttachments(ProtocolRequest protocolRequest, RpcInvocation rpcInvocation) {
        RpcContext context = RpcContext.getContext();
        InetSocketAddress remoteAddress = new InetSocketAddress(protocolRequest.getRemoteAddr(), protocolRequest.getRemotePort());
        context.setRemoteAddress(remoteAddress);

        for (String headerKey : Consts.AttachmentKeys) {
            String headerValue = protocolRequest.getHeader(headerKey);
            rpcInvocation.setAttachment(headerKey, headerValue);
        }
    }

    /**
     * 读取参数值，做必要的转换，Primitive类型空值处理
     */
    protected Object parseArgumentValue(Class<?> matchedInterface, Method matchedMethod, Integer parameterIndex, Class<?> parameterType, Object argumentValue) {
        if (null == argumentValue) { // 空参数处理
            return this.parseNullArgument(parameterType, argumentValue);
        }

        // 非空参数类型检查
        Class<?> argumentValueType = argumentValue.getClass();

        // 数组，集合参数转换
        if (argumentValue instanceof JSONArray) {
            return this.parseArrayArgument(matchedInterface, matchedMethod, parameterIndex, parameterType, (JSONArray) argumentValue);
        }

        // 时间参数转换
        if (Date.class.isAssignableFrom(parameterType)) {
            return this.parseDateArgument(matchedInterface, matchedMethod, parameterIndex, parameterType, argumentValue);
        }

        // 枚举参数处理
        if (parameterType.isEnum()) {
            return this.parseEnumArgument(matchedInterface, matchedMethod, parameterIndex, parameterType, argumentValue);
        }

        // 对象Map参数转换
        if (argumentValue instanceof JSONObject) {
            return this.parseJsonObjectArgument(matchedInterface, matchedMethod, parameterIndex, parameterType, (JSONObject) argumentValue);
        }

        // 数值类型转换
        if (ReflectUtil.isNumberType(parameterType)) {
            return NumberUtil.toNumber(argumentValue, parameterType);
        }

        // 类型匹配成功
        if (BudoReflectionUtil.isArgumentValueTypeMatchParameterType(parameterType, argumentValueType)) {
            return argumentValue;
        }

        // 兜底 string 可接受所有类型参数
        if (String.class.equals(parameterType)) {
            return argumentValue.toString();
        }

        String parameterName = BudoReflectionUtil.getParameterNameCached(matchedInterface, matchedMethod, parameterIndex);

        ArgumentTypeMismatchException argumentTypeMismatchException = new ArgumentTypeMismatchException(matchedMethod, parameterIndex, parameterType, parameterName, argumentValue);

        log.error("#173 argumentTypeMismatchException=" + argumentTypeMismatchException);
        throw argumentTypeMismatchException;
    }

    private Object parseJsonObjectArgument(Class<?> matchedInterface, Method matchedMethod, Integer parameterIndex, Class<?> parameterType, JSONObject jsonObject) {
        if (parameterType.isArray()) { // 需要传入数组
            String parameterName = BudoReflectionUtil.getParameterNameCached(matchedInterface, matchedMethod, parameterIndex);
            throw new ArrayArgumentMismatchException(matchedMethod, parameterIndex, parameterType, parameterName, jsonObject);
        }

        // TODO 如果需要的是一个泛型的Map，Map的Value是User
        try {
            return jsonObject.toJavaObject(parameterType);
        } catch (Throwable e) { // com.alibaba.fastjson.JSONException: can not cast to Timestamp, value
                                // :2017-11-01 00:00
            log.error("#177 parseJsonObjectArgument error, jsonObject=" + jsonObject //
                    + ", parameterType=" + parameterType //
                    + ", matchedMethod=" + matchedMethod //
                    + ", parameterIndex=" + parameterIndex, e);

            String parameterName = BudoReflectionUtil.getParameterNameCached(matchedInterface, matchedMethod, parameterIndex);
            throw new ArgumentTypeMismatchException(matchedMethod, parameterIndex, parameterType, parameterName, jsonObject);
        }
    }

    /**
     * 枚举参数错误
     */
    private Enum parseEnumArgument(Class<?> matchedInterface, Method matchedMethod, Integer parameterIndex, Class<?> parameterType, Object argumentValue) {
        if (!argumentValue.getClass().equals(String.class)) {
            String parameterName = BudoReflectionUtil.getParameterNameCached(matchedInterface, matchedMethod, parameterIndex);
            throw new EnumArgumentMismatchException(matchedMethod, parameterIndex, parameterType, parameterName, argumentValue);
        }

        try {
            return Enum.valueOf((Class<? extends Enum>) parameterType, (String) argumentValue);
        } catch (IllegalArgumentException e) { // No enum constant
            String parameterName = BudoReflectionUtil.getParameterNameCached(matchedInterface, matchedMethod, parameterIndex);
            throw new EnumArgumentMismatchException(matchedMethod, parameterIndex, parameterType, parameterName, argumentValue);
        }
    }

    /**
     * json 数组 参数处理
     */
    private Object parseArrayArgument(Class<?> matchedInterface, Method matchedMethod, Integer parameterIndex, Class<?> parameterType, JSONArray jsonArray) {
        int arraySize = jsonArray.size();

        if (!parameterType.isArray() //
                && !Object.class.equals(parameterType) //
                && !Collection.class.isAssignableFrom(parameterType)) { // 需要的参数类型不是数组或集合，但传入的是数组或集合
            if (arraySize < 1) {
                return null;
            }

            if (arraySize > 1) {
                String message = "#232 jsonArray 转 parameterType=" + parameterType + " 未支持";
                throw new BudoRuntimeException(message);
            }

            // 传入只有一个值的数组时, 兼容一下
            Object argumentValue = jsonArray.get(0);
            return this.parseArgumentValue(matchedInterface, matchedMethod, parameterIndex, parameterType, argumentValue);
        }

        Class<?> componentType = BudoReflectionUtil.getComponentType(matchedMethod, parameterIndex);

        if (null == componentType) {
            log.warn("#245 componentType=" + componentType + ", matchedMethod=" + matchedMethod + ", parameterIndex=" + parameterIndex);
            componentType = Object.class;
        }

        Object[] arrayArgument = (Object[]) Array.newInstance(componentType, arraySize);

        for (int i = 0; i < arraySize; i++) { // 每一个参数再单独转换一遍，确保类型匹配
            Object argumentValue = jsonArray.get(i);
            arrayArgument[i] = this.parseArgumentValue(matchedInterface, matchedMethod, parameterIndex, componentType, argumentValue);
        }

        if (parameterType.isArray()) {
            return arrayArgument;
        }

        if (List.class.equals(parameterType)) {
            return new ArrayList(Arrays.asList(arrayArgument));
        }

        if (Set.class.equals(parameterType)) {
            return new HashSet(Arrays.asList(arrayArgument));
        }

        if (Object.class.equals(parameterType)) {
            return arrayArgument;
        }

        String message = "#266 jsonArray 转 parameterType=" + parameterType + " 未支持";
        throw new BudoRuntimeException(message);
    }

    /**
     * 空值处理
     */
    private Object parseNullArgument(Class<?> parameterType, Object argumentValue) {
        if (int.class.equals(parameterType)) {
            return 0;
        }

        if (boolean.class.equals(parameterType)) {
            return false;
        }

        if (char.class.equals(parameterType)) {
            return (char) 0;
        }

        if (byte.class.equals(parameterType)) {
            return (byte) 0;
        }

        if (short.class.equals(parameterType)) {
            return (short) 0;
        }

        if (long.class.equals(parameterType)) {
            return (long) 0;
        }

        if (float.class.equals(parameterType)) {
            return (float) 0;
        }

        if (double.class.equals(parameterType)) {
            return (double) 0;
        }

        return argumentValue;
    }

    /**
     * 时间类型参数处理
     */
    protected Date parseDateArgument(Class<?> matchedInterface, Method matchedMethod, Integer parameterIndex, Class<?> parameterType, Object argumentValue) {
        if (null == argumentValue //
                || (argumentValue instanceof String && StringUtils.isEmpty((String) argumentValue))) {
            return null; // 值为空
        }

        TimePoint timePoint = Time.when(argumentValue, this.getTimeFormats());

        // 这里 parameterNames 有可能为空，如果实现类是普通类就应该取得到，如果是 DubboReference 就取不到
        String parameterName = BudoReflectionUtil.getParameterNameCached(matchedInterface, matchedMethod, parameterIndex);

        // 时间 timeFormat parse 失败
        if (null == timePoint) {
            throw new ArgumentTimeFormatException(parameterIndex, parameterType, parameterName, argumentValue, this.getTimeFormats());
        }

        if (parameterType.equals(java.util.Date.class)) {
            return timePoint.toDate();
        }

        if (parameterType.equals(java.sql.Time.class)) {
            return timePoint.toSqlTime();
        }

        if (parameterType.equals(Timestamp.class)) {
            return timePoint.toTimestamp();
        }

        throw new ArgumentTypeMismatchException(matchedMethod, parameterIndex, parameterType, parameterName, argumentValue);
    }
}